#include "types.h"
#include "io.h"
#include "f1c100s/reg-tcon.h"
#include "f1c100s/reg-defe.h"
#include "f1c100s/reg-debe.h"
#include "f1c100s/reg-ccu.h"
#include "f1c100s/reg-gpio.h"

struct f1c100s_tcon_pdata_t
{
	int clktcon;
	int width;
	int height;
	int bits_per_pixel;
	void * vram;

	struct {
		int pixel_clock_hz;
		int h_front_porch;
		int h_back_porch;
		int h_sync_len;
		int v_front_porch;
		int v_back_porch;
		int v_sync_len;
		int h_sync_active;
		int v_sync_active;
		int den_active;
		int clk_active;
	} timing;
};

static inline void f1c100s_debe_set_mode(struct f1c100s_tcon_pdata_t * pdat)
{
	struct f1c100s_debe_reg_t * debe = (struct f1c100s_debe_reg_t *)F1C100S_DEBE_BASE;
	u32_t val;

	val = read32((virtual_addr_t)&debe->mode);
	val |= (1 << 0);
	write32((virtual_addr_t)&debe->mode, val);

	write32((virtual_addr_t)&debe->disp_size, (((pdat->height) - 1) << 16) | (((pdat->width) - 1) << 0));
	write32((virtual_addr_t)&debe->layer0_size, (((pdat->height) - 1) << 16) | (((pdat->width) - 1) << 0));
	write32((virtual_addr_t)&debe->layer0_stride, ((pdat->width) << 5));
	write32((virtual_addr_t)&debe->layer0_addr_low32b, (u32_t)(pdat->vram) << 3);
	write32((virtual_addr_t)&debe->layer0_addr_high4b, (u32_t)(pdat->vram) >> 29);
	write32((virtual_addr_t)&debe->layer0_attr1_ctrl, 0x09 << 8);

	val = read32((virtual_addr_t)&debe->mode);
	val |= (1 << 8);
	write32((virtual_addr_t)&debe->mode, val);

	val = read32((virtual_addr_t)&debe->reg_ctrl);
	val |= (1 << 0);
	write32((virtual_addr_t)&debe->reg_ctrl, val);

	val = read32((virtual_addr_t)&debe->mode);
	val |= (1 << 1);
	write32((virtual_addr_t)&debe->mode, val);
}

void f1c100s_debe_set_address(void *vram)
{
	struct f1c100s_debe_reg_t * debe = (struct f1c100s_debe_reg_t *)F1C100S_DEBE_BASE;

	write32((virtual_addr_t)&debe->layer0_addr_low32b, (u32_t)vram << 3);
	write32((virtual_addr_t)&debe->layer0_addr_high4b, (u32_t)vram >> 29);
}

static inline void f1c100s_tcon_enable(struct f1c100s_tcon_pdata_t * pdat)
{
	struct f1c100s_tcon_reg_t * tcon = (struct f1c100s_tcon_reg_t *)F1C100S_TCON_BASE;
	u32_t val;

	val = read32((virtual_addr_t)&tcon->ctrl);
	val |= (1u << 31);
	write32((virtual_addr_t)&tcon->ctrl, val);
}

static inline f1c100s_tcon_disable(struct f1c100s_tcon_pdata_t * pdat)
{
	struct f1c100s_tcon_reg_t * tcon = (struct f1c100s_tcon_reg_t *)F1C100S_TCON_BASE;
	u32_t val;

	write32((virtual_addr_t)&tcon->ctrl, 0);
	write32((virtual_addr_t)&tcon->int0, 0);

	val = read32((virtual_addr_t)&tcon->tcon0_dclk);
	val &= ~(0xfu << 28);
	write32((virtual_addr_t)&tcon->tcon0_dclk, val);

	write32((virtual_addr_t)&tcon->tcon0_io_tristate, 0xffffffff);
	write32((virtual_addr_t)&tcon->tcon1_io_tristate, 0xffffffff);
}

static inline void f1c100s_tcon_set_mode(struct f1c100s_tcon_pdata_t * pdat)
{
	struct f1c100s_tcon_reg_t * tcon = (struct f1c100s_tcon_reg_t *)F1C100S_TCON_BASE;
	int bp, total;
	u32_t val;

	val = read32((virtual_addr_t)&tcon->ctrl);
	val &= ~(0x1 << 0);
	write32((virtual_addr_t)&tcon->ctrl, val);

	val = (pdat->timing.v_front_porch + pdat->timing.v_back_porch + pdat->timing.v_sync_len);
	write32((virtual_addr_t)&tcon->tcon0_ctrl, (1u << 31) | ((val & 0x1f) << 4));
	val = pdat->clktcon / pdat->timing.pixel_clock_hz;
	write32((virtual_addr_t)&tcon->tcon0_dclk, (0xfu << 28) | (val << 0));
	write32((virtual_addr_t)&tcon->tcon0_timing_active, ((pdat->width - 1) << 16) | ((pdat->height - 1) << 0));

	bp = pdat->timing.h_sync_len + pdat->timing.h_back_porch;
	total = pdat->width + pdat->timing.h_front_porch + bp;
	write32((virtual_addr_t)&tcon->tcon0_timing_h, ((total - 1) << 16) | ((bp - 1) << 0));
	bp = pdat->timing.v_sync_len + pdat->timing.v_back_porch;
	total = pdat->height + pdat->timing.v_front_porch + bp;
	write32((virtual_addr_t)&tcon->tcon0_timing_v, ((total * 2) << 16) | ((bp - 1) << 0));
	write32((virtual_addr_t)&tcon->tcon0_timing_sync, ((pdat->timing.h_sync_len - 1) << 16) | ((pdat->timing.v_sync_len - 1) << 0));

	write32((virtual_addr_t)&tcon->tcon0_hv_intf, 0);
	write32((virtual_addr_t)&tcon->tcon0_cpu_intf, 0);

	if(pdat->bits_per_pixel == 18 || pdat->bits_per_pixel == 16)
	{
		write32((virtual_addr_t)&tcon->tcon0_frm_seed[0], 0x11111111);
		write32((virtual_addr_t)&tcon->tcon0_frm_seed[1], 0x11111111);
		write32((virtual_addr_t)&tcon->tcon0_frm_seed[2], 0x11111111);
		write32((virtual_addr_t)&tcon->tcon0_frm_seed[3], 0x11111111);
		write32((virtual_addr_t)&tcon->tcon0_frm_seed[4], 0x11111111);
		write32((virtual_addr_t)&tcon->tcon0_frm_seed[5], 0x11111111);
		write32((virtual_addr_t)&tcon->tcon0_frm_table[0], 0x01010000);
		write32((virtual_addr_t)&tcon->tcon0_frm_table[1], 0x15151111);
		write32((virtual_addr_t)&tcon->tcon0_frm_table[2], 0x57575555);
		write32((virtual_addr_t)&tcon->tcon0_frm_table[3], 0x7f7f7777);
		write32((virtual_addr_t)&tcon->tcon0_frm_ctrl, (pdat->bits_per_pixel == 18) ? ((1u << 31) | (0 << 4)) : ((1u << 31) | (5 << 4)));
	}

	val = (1 << 28);
	if(!pdat->timing.h_sync_active)
		val |= (1 << 25);
	if(!pdat->timing.v_sync_active)
		val |= (1 << 24);
	if(!pdat->timing.den_active)
		val |= (1 << 27);
	if(!pdat->timing.clk_active)
		val |= (1 << 26);
	write32((virtual_addr_t)&tcon->tcon0_io_polarity, val);
	write32((virtual_addr_t)&tcon->tcon0_io_tristate, 0);
}

static inline void f1c100s_tcon_init(struct f1c100s_tcon_pdata_t * pdat)
{
	f1c100s_tcon_disable(pdat);
	f1c100s_debe_set_mode(pdat);
	f1c100s_tcon_set_mode(pdat);
	f1c100s_tcon_enable(pdat);
}

static void f1c100s_tcon_clock_init(void)
{
	u32_t i, val;

	val = read32(F1C100S_CCU_BASE + CCU_DEFE_CLK);
	val &= ~((7 << 24) | (0xf << 0));
	val |= 1u << 31;
	write32(F1C100S_CCU_BASE + CCU_DEFE_CLK, val);
	
	val = read32(F1C100S_CCU_BASE + CCU_DEBE_CLK);
	val &= ~((7 << 24) | (0xf << 0));
	val |= 1u << 31;
	write32(F1C100S_CCU_BASE + CCU_DEBE_CLK, val);
	
	val = read32(F1C100S_CCU_BASE + CCU_LCD_CLK);
	val &= ~(7 << 24);
	val |= 1u << 31;
	write32(F1C100S_CCU_BASE + CCU_LCD_CLK, val);
	
	val = read32(F1C100S_CCU_BASE + CCU_BUS_CLK_GATE1);
	val |= (1 << 4) | (1 << 12) | (1 << 14);
	write32(F1C100S_CCU_BASE + CCU_BUS_CLK_GATE1, val);
	
	val = read32(F1C100S_CCU_BASE + CCU_BUS_SOFT_RST1);
	val |= (1 << 4) | (1 << 12) | (1 << 14);
	write32(F1C100S_CCU_BASE + CCU_BUS_SOFT_RST1, val);

	for(i = 0x0800; i < 0x1000; i += 4)
	{
		write32(F1C100S_DEBE_BASE + i, 0);
	}
}

static inline void f1c100s_tcon_gpio_init(void)
{
	GPIOD->CFG[0] = 0x22222222;
	GPIOD->CFG[1] = 0x22222222;
	GPIOD->CFG[2] = 0x00222222;

	GPIOE->CFG[0] &= ~(0x0F<<6*4);
	GPIOE->CFG[0] |= (1<<6*4);
	GPIOE->DATA |= 1<<6;
}

const struct f1c100s_tcon_pdata_t tcon_pdat = {198000000, 480, 270, 18, (void *)0x80000000, {33000000, 40, 87, 1, 13, 31, 1, 0, 0, 1, 1}};
void sys_tcon_init(void)
{
	f1c100s_tcon_gpio_init();
	f1c100s_tcon_clock_init();
	f1c100s_tcon_init((struct f1c100s_tcon_pdata_t *)&tcon_pdat);
}
